import { Dimensions } from "react-native";
import RNFS from "react-native-fs";
import fso from "@/utils/FileOperate/operate";
import NetInfo from "@react-native-community/netinfo";

import dayjs from "dayjs";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";

import store from "@/store";

import { getPlan } from "@/api/videoDisplay";
import { MaterialSqliteType, ScheduleSqliteType, materialSqlh, scheduleSqlh } from "@/sqlite/exports";
import config from "@/config";
import { setCurPlayData, setCurSchedule, setNextMaterial } from "@/store/slices";
import { params2Obj } from "@/utils";
import { getFmtTime, timeToSecond } from "@/utils/date";

import type { ScheduleType, MaterialType } from "@/typings/businessType";
import type { CurPlayDataType } from "@/typings/storeType";
import type { OnLoadData } from "react-native-video";
import type { FetchBlobResponse, StatefulPromise } from "react-native-blob-util";

dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);

/** 查找到的排期数据 */
export type SeekResultType = {
  /** 满足的排期(内包含素材)数据 */
  scheduleList: Array<ScheduleSqliteType & { materialList: MaterialSqliteType[] }>;
  /** 等待下载的的素材数据 */
  waitdownloadMaterialList: MaterialSqliteType[];
  /** 已经下载完成的素材数据 */
  downloadedMaterialList: MaterialSqliteType[];
};

/** 查找到的素材数据 */
export type SeekCurTimeMaterialDataType = {
  /** 当前需要播放的素材 */
  curMaterial: MaterialSqliteType;
  /** 下一需要播放的素材 */
  nextMaterial: MaterialSqliteType | null;
  /** 当前播放的排期数据 */
  curSchedule: SeekResultType["scheduleList"];
};

/**
 * 排期操作类, 包括:
 *  排期查找, 清理排期和素材数据, 管理素材下载, 检验素材等
 */
export default class ScheduleManage {
  /** 定时同步排期延时器id */
  #syncScheduleTimer: number = 0;

  /** 当前是否正在下载素材文件 */
  #isDownMaterial = false;

  /** 并发下载素材的数量 */
  #limit = config.downMaterialLinit;

  /** 延时器id */
  #timer = 0;

  /** 当前是否正在定时查询素材 */
  isLoopSeek = false;
  /** 查找间隔, 以免下载素材太快, 查找过快导致性能问题 */
  #loopSeekInterval = 5000;
  /** 上次查找的时间 */
  #lastLoopSeekTime = 0;
  /** 是否初次查询排期 */
  #isFirshLoopSeek = true;

  /** 当前正在下载的任务信息 */
  downloadingTaskData: {
    task: StatefulPromise<FetchBlobResponse> | null;
    material: MaterialSqliteType;
  }[] = [];

  /**
   * 是否允许下载的标识, 用于取消下载
   */
  #downloadingFlag = true;

  /** 循环播放素材的间隔时长 */
  loopPlayInterval = config.materialoopPlayInterval;

  /** 保存 playMaterialByFile 需要播放的下一文件路径 */
  #nextPlayFilePath = "";

  /** 播放旧版app资源, 用于迁移后的播放 */
  #playOldAppResource = false;

  /** playMaterialByTime 定时查找素材播放需要加多3秒钟, 因为定时器可能不是很准 */
  #deviationInterval = 3000;

  /** pdf定时翻页定时器 */
  #pdfSetTimeoutTimer: NodeJS.Timeout | null = null;
  /** pdf当前播放的信息 */
  #pdfCurrentInfo = {
    filePath: "",
    page: -1
  };

  constructor() {}

  public log(...args: any[]) {
    if (config.IS_DEBUG) {
      console.log(dayjs().format("HH:mm:ss"), ...args);
    }
  }

  /**
   * 同步排期数据, 里面很粗暴直接把本地的排期全部删除然后重新插入最新的
   */
  public syncSchedule() {
    return new Promise<boolean>(async resolve => {
      try {
        const storeRedux = store.getState();
        const deviceCode = storeRedux.index.entryNetworkInfo?.code || "";
        fso.info("设备编号: ", deviceCode);

        const handleClearFn = async () => {
          try {
            if (scheduleSqlh) {
              const clearRes = await this.clearNativeData(false);
              fso.warn("本地数据库数据清除: ", clearRes ? "成功" : "失败");
              if (!deviceCode) return;

              const res = await scheduleSqlh.getList();
              if (res.length === 0) {
                const ret = await this.isOldAppMigration();
                if (ret) {
                  fso.info("========== 依次播放旧版app已经下载的素材 ==========");
                  this.#playOldAppResource = true;
                  this.removeOldResourceOtherFile();
                  this.playMaterialByFile(config.oldAppInfo.resourceDir);
                } else {
                  fso.info("========== 依次播放所有已下载的素材 ==========");
                  // 这里把这个播放旧版的标识也设置为true
                  this.#playOldAppResource = true;
                  // 但是这里是查询新版的素材目录, 这样本地没有没有排期也可以播放已经下载的素材
                  this.playMaterialByFile();
                }
              }
            } else {
              fso.error("数据库初始化失败");
              this.playPolyfill();
            }
          } catch (err) {
            fso.error("数据库数据清理失败");
            this.playPolyfill();
          }

          return Promise.resolve();
        };

        if (!deviceCode) {
          fso.warn("没有获取到设备编号, 清除本地的排期数据");
          await handleClearFn();
          resolve(true);
          return;
        }

        const res = await getPlan(deviceCode);
        if (!res.data) {
          fso.info("后台没有获取到排期数据: ", res);
          await handleClearFn();
          resolve(true);
          return;
        }
        fso.info("后台获取到排期数据: ", res.data);
        this.#playOldAppResource = false;
        this.stopSeek();
        const schedule: ScheduleType & { materials: MaterialType[] } = res.data;
        if (Array.isArray(schedule.materials) && schedule.materials.length) {
          const ret = await this.saveSchedule(schedule);
          // TODO
          // console.log("更新到后台");
          this.loopSeek();
          resolve(ret);
        } else {
          fso.warn(`【${schedule.name}】排期没有素材`);
          resolve(true);
        }
      } catch (err: any) {
        this.log("err: ", err);
        // fso.error("同步排期失败, 30s后自动同步排期数据: ", err);

        // TODO 如果网络错误则会一直重试
        // this.#syncScheduleTimer && clearTimeout(this.#syncScheduleTimer);
        // // @ts-ignore
        // this.#syncScheduleTimer = setTimeout(() => {
        //   this.syncSchedule();
        // }, 30 * 1000);

        this.playPolyfill();
        resolve(false);
      }
    });
  }

  /**
   * 保存排期数据
   */
  public saveSchedule(schedule: ScheduleType & { materials: MaterialType[] }) {
    return new Promise<boolean>(async resolve => {
      try {
        // 清空本地排期和素材数据, 但不清空素材文件数据
        const cleatRes = await this.clearNativeData(false);
        if (!cleatRes) {
          fso.warn("本地排期和素材未清理成功");
        }

        if (scheduleSqlh) {
          this.log("插入新增的排期数据");
          const insertRes = await scheduleSqlh.insert<ScheduleSqliteType>({
            ...schedule,
            sourceId: schedule.id
          });
          fso.info("插入排期数据sql响应", insertRes);
          if (materialSqlh) {
            this.log("清空素材表数据");
            const materialClearRes = await materialSqlh.clearTable();
            this.log("清空素材表数据sql响应", materialClearRes);

            const materials: MaterialSqliteType[] = [];

            for (let i = 0; i < schedule.materials.length; i++) {
              const material = schedule.materials[i];

              /** 截取文件名称 */
              const obj = params2Obj({
                str: material.file,
                decode: false
              });

              // /** 截取url后面的文件名称 */
              const urlPop = material.file.split("?")[0]?.split("/")?.pop() || "";

              const fileName = obj?.name || urlPop || material.name;

              /** 下载到本地的路径 */
              const downFileToNativePath = `${fso.resourceDir}/${schedule.code}/${fileName}`;

              /** 素材下载的地址 */
              const downFileUrl = material.file.startsWith("http")
                ? material.file
                : config.proxy("/platform" + material.file);

              /** 是否已经下载 */
              const isDown = await RNFS.exists(downFileToNativePath);
              /** 如果是已经下载的, 还需要检验下载的文件有没有问题 */
              if (isDown) {
                const size = Number(material.size);
                const stat = await RNFS.stat(downFileToNativePath);
                if (size !== stat.size) {
                  fso.warn(
                    `检查到【${material.name}】素材异常`,
                    `本地文件大小(${stat.size}bytes)跟后台素材数据(${size}bytes)不一致, 请检查本地文件: `,
                    downFileToNativePath
                  );

                  fso.info("删除异常的文件: ", downFileToNativePath);
                  fso.rm(downFileToNativePath);
                }
              }

              /** 是否分片下载  */
              const isChunkDown = Number(material.size) > config.downMaterialChunkSize;

              const sourceId = material.id;
              // @ts-ignore
              delete material.id;

              // @ts-ignore
              materials.push({
                ...material,
                fileName,
                // downFileToNativePath: 'file://' + downFileToNativePath,
                downFileToNativePath: downFileToNativePath,
                downFileUrl,
                isDown: isDown ? "2" : "1",
                isChunkDown: isChunkDown ? "2" : "1",
                sourceId,
                parentCode: schedule.code

                // 不需要id, inserts 里面会自动过滤主键对应的key
                // id: 1,
              });
            }
            const materiaInsertRes = await materialSqlh.inserts(materials);
            fso.info("插入素材数据sql响应", materiaInsertRes);
          } else {
            fso.info("素材数据库初始化失败");
            resolve(false);
            return;
          }
        } else {
          fso.error("排期数据库初始化失败");
          resolve(false);
          return;
        }
        resolve(true);
      } catch (err: any) {
        this.log("err: ", err);
        fso.error("发生了错误: ", err.message);
        resolve(false);
      }
    });
  }

  /** 查找符合当前日期播放的排期数据 */
  public seekCurTimeScheduleData() {
    return new Promise<SeekResultType | null>(async resolve => {
      try {
        if (scheduleSqlh) {
          const startDate = dayjs().format("YYYY-MM-DD HH:mm:ss");
          const [sql] = scheduleSqlh.sqliteOrm
            .select()
            .fillValue(false)
            .where("startDate", "<=", startDate)
            .getSqlRaw();

          if (this.#isFirshLoopSeek) {
            fso.info(`查找本地排期数据sql: `, sql);
          }
          const executRes = await scheduleSqlh.executeSql(sql);
          const startDataList = scheduleSqlh.buildRet<SeekResultType["scheduleList"]>(executRes);
          if (this.#isFirshLoopSeek) {
            fso.info(
              "满足开始时间的排期: ",
              startDataList.map(e => e.name)
            );
          }

          // 当前日期在结束日期之前
          const scheduleList = startDataList.filter(e => dayjs().isBefore(dayjs(e.endDate)));
          if (this.#isFirshLoopSeek) {
            fso.info(
              "满足结束日期的排期",
              startDataList.map(e => e.name)
            );
          }

          if (scheduleList.length === 0) {
            if (this.#isFirshLoopSeek) {
              fso.info(`过期的排期: `, startDataList);
            }
            resolve(null);
            return;
          }

          const parentCodes = scheduleList.map(e => e.code);
          if (materialSqlh) {
            const materialList = await materialSqlh.getByArray<MaterialSqliteType[]>(parentCodes, "parentCode", "OR");
            this.log(
              `查找对应排期下面的素材: ${parentCodes}`,
              materialList.map(e => e.name)
            );

            /**
             * 这里读取本地已经下载的素材数据然后跟本地数据库数据对比一下
             * 防止本地数据库的数据跟本地已经下载的数据不对应
             * 1. 本地没有下载, 但是数据库里已经是下载的会造成漏下载
             * 2. 本地已经下载, 但是数据库里是未下载会造成重复下载
             */
            const items = await this.getDownloadedMaterial();
            const paths = items.map(e => e.path);
            this.log("本地已经下载的素材数据: ", [...new Set(paths)]);

            // 数据库保存是已下载但实际未下载的素材
            const notDown: MaterialSqliteType[] = [];

            for (let i = 0; i < materialList.length; i++) {
              const material = materialList[i];
              // 数据库保存是已下载但实际没有读取到下载的文件
              if (!paths.includes(material.downFileToNativePath)) {
                // fso.readDir 方法有时会出现实际文件存在但是读取不出来的问题, 这里在判断一下, 文件是否存在
                const exists = await fso.exists(material.downFileToNativePath);
                if (!exists) {
                  notDown.push(material);
                  material.isDown = "1";
                }
              }
            }

            materialList.forEach(e => {
              // 数据库保存是已下载但实际未下载的素材
              if (!paths.includes(e.downFileToNativePath)) {
                notDown.push(e);
                e.isDown = "1";
              }
            });

            if (notDown.length) {
              this.log("数据库已下载但是本地未下载的素材数据: ", notDown);
              const whereSql = notDown.map(e => `downFileToNativePath="${e.downFileToNativePath}"`).join(" OR ");
              const sql = `UPDATE "${materialSqlh.tableName}" SET isDown="1" WHERE ${whereSql}`;
              const results = await materialSqlh.executeSql(sql);
              fso.info("更新本地素材下载状态响应: ", results);
            }

            scheduleList.forEach(e => {
              const list = materialList.filter(item => item.parentCode === e.code);
              e.materialList = list;
            });

            this.log("检查待下载的素材");
            const waitdownloadMaterialList = scheduleList.flatMap(e => {
              const downloadedInfo = e.materialList.filter(item => item.isDown === "2");

              this.log(
                `【${e.name}】排期已下载的素材信息如下: `,
                downloadedInfo.map(e => e.name)
              );

              // 这里直接先判断下载时间避免写入太多的日志
              const ymd = dayjs().format("YYYY-MM-DD");
              const isSomeDate = dayjs().isBefore(dayjs(`${ymd} ${config.startDownMaterialDate}`));
              if (isSomeDate) {
                fso.info(`不能下载素材, 不符合下载条件: 指定下载时间 ${config.startDownMaterialDate}点后下载`);
                return [];
              } else {
                return e.materialList.filter(this.checkDownMateriaData);
              }
            });
            if (waitdownloadMaterialList.length) {
              fso.info(`待下载的素材数据${waitdownloadMaterialList.length}个: `, waitdownloadMaterialList);
            }

            // 已经下载完成的素材
            const downloadedMaterialList = scheduleList.flatMap(e =>
              e.materialList.filter(item => item.isDown === "2")
            );
            this.log(
              "已经下载的素材数据: ",
              downloadedMaterialList.map(e => e.name)
            );

            resolve({
              scheduleList,
              waitdownloadMaterialList,
              downloadedMaterialList
            });
          } else {
            fso.error("素材数据库初始失败");
            resolve(null);
          }
        } else {
          fso.error("排期数据库初始化失败");
          resolve(null);
        }
      } catch (err) {
        fso.error("查找排期数据发生了错误: ", err);
        resolve(null);
      }
    });
  }

  /** 下载排期素材数据 */
  public async downMaterialData(opt: {
    /** 待下载的素材数据 */
    materialList: MaterialSqliteType[];
    /**
     * 下载并发数, 没用上传因为设备的性能不好, 并发下载会导致页面卡顿
     */
    limit?: number;
  }) {
    const res = await NetInfo.fetch();
    if (!res.isConnected) {
      fso.info("当前没有网络不下载素材");
      return;
    }

    if (this.#isDownMaterial) {
      fso.info("当前正在下载素材");
      return;
    }

    // 这里要根据素材的下载的本地位置去重, 因为可能包括相同的素材
    const downData: MaterialSqliteType[] = [];
    opt.materialList.forEach(e => {
      const idx = downData.findIndex(item => item.downFileToNativePath === e.downFileToNativePath);
      if (idx === -1) downData.push(e);
    });

    this.#isDownMaterial = true;
    fso.info("开始下载素材", downData);
    this.downloadingTaskData = downData.map(e => {
      return {
        task: null,
        material: e
      };
    });

    /** 更新本地下载素材的信息 */
    const updateDownFileData = (path: string | Error | void, item: MaterialSqliteType) => {
      if (typeof path === "string") {
        if (materialSqlh) {
          materialSqlh
            .updateByCustom<MaterialSqliteType>({ isDown: "2" }, { downFileToNativePath: item.downFileToNativePath })
            .then(res => {
              fso.warn(`【${item.name}】素材下载状态更新为已下载:`, res);

              // TODO
              // console.log("更新到后台");

              this.loopSeek();
            })
            .catch(err => {
              this.log("err: ", err);
              fso.warn(`【${item.name}】素材下载状态更新失败:`, err);
            });
        } else {
          fso.warn("素材SQLite数据库初始化失败");
        }
      } else {
        fso.error(`【${item.name}】下载失败, 错误信息: `, path);
        fso.error(`【${item.name}】下载失败, 素材信息: `, item);
      }
    };

    try {
      for (let i = 0; i < downData.length; i++) {
        const material = downData[i];

        if (!this.#downloadingFlag) {
          fso.info("取消后续的所有下载任务", downData.slice(i));
          return;
        }

        this.downloadingTaskData[i] = { task: null, material };
        // 这里一个一个的下载文件, 如果并发去下载的话, 特别是分片下载里面会有合并分片文件的操作, 会照成很大的性能问题
        const res1 = await fso
          .downFile(
            {
              url: material.downFileUrl,
              method: "GET",
              title: `多媒体信息素材下载`,
              description: `下载${material.name}素材中`,
              downFileToNativePath: material.downFileToNativePath
            },
            task => {
              // 收集下载任务实例
              this.downloadingTaskData[i].task = task;
            }
          )
          .catch(err => {
            fso.error(`【${material.name}】下载失败, 错误信息: `, err);
            fso.error(`【${material.name}】下载失败, 素材信息: `, material);
          })
          .finally(() => {
            this.downloadingTaskData.splice(i, 1);
            this.log("弹出下载任务队列: ", this.downloadingTaskData);
          });

        updateDownFileData(res1, material);

        // if (material.isChunkDown === "1") {
        //   const res1 = await fso
        //     .downFile({
        //       url: material.downFileUrl,
        //       method: "GET",
        //       title: `多媒体信息素材下载`,
        //       description: `下载${material.name}素材中`,
        //       downFileToNativePath: material.downFileToNativePath
        //     })
        //     .catch(err => {
        //       fso.error(`【${material.name}】下载失败, 错误信息: `, err);
        //       fso.error(`【${material.name}】下载失败, 素材信息: `, material);
        //     });
        //   updateDownFileData(res1, material);
        // } else if (material.isChunkDown === "2") {
        // TODO 分片下载合并文件的时候, 刚好就查找到需要播放当前合并的文件, 会导致大小不匹配被删除
        //   const res2 = await fso
        //     .sliceDownload({
        //       url: material.downFileUrl,
        //       method: "GET",
        //       fileSize: Number(material.size),
        //       rangeSize: config.downMaterialChunkSize,
        //       restDownload: true,
        //       downloadFilePath: material.downFileToNativePath,
        //       onSomeRestDownload(fileList) {
        //         this.log("本地存在还有未下载完成的切片文件", fileList);
        //       },
        //       onCurrentRestDownload(fileList) {
        //         this.log(`要下载的 ${this.downloadFilePath} 有未下载成功的部分, 继续下载剩余的部分`);
        //       },
        //       onSliceFileDownError(fileInfo) {
        //         this.log(`${fileInfo.targetFilePath} 下载失败`);
        //       },
        //       onBeforeSliceDownFile(opt) {
        //         // this.log("开始下载: ", opt);
        //       },
        //       onAfterSliceDownFile(opt) {
        //         this.log("下载完成: ", opt);
        //       },
        //       onUnfinishFileDown(opt) {
        //         this.log(`分片文件未下载完成`, opt);
        //       },
        //       onBuildSliceFile(opt) {
        //         this.log(`下载完成, 开始合并分片文件`, opt);
        //       }
        //     })
        //     .catch(err => {
        //       fso.error(`【${material.name}】下载失败, 错误信息: `, err);
        //       fso.error(`【${material.name}】下载失败, 素材信息: `, material);
        //     });
        //   updateDownFileData(res2, material);
        // }
      }
    } catch (err) {
      fso.error("下载失败了: ", err);
    } finally {
      this.#isDownMaterial = false;
      this.#downloadingFlag = true;
      this.downloadingTaskData = [];
    }
  }

  /** 取消当前以及后续的素材下载 */
  public cancelDownMaterial() {
    this.#downloadingFlag = true;
    this.#isDownMaterial = false;
    this.downloadingTaskData.forEach(e => {
      e.task?.cancel();
      fso.info("取消当前的下载任务", e.material);
    });

    // const notDownload = this.downloadingTaskData.filter(e => !e.task).map(e => e.material);
    // fso.info("取消后续的所有下载任务", notDownload);
  }

  /** 查找符合当前时间的素材数据  */
  public seekCurTimeMaterialData() {
    return new Promise<SeekCurTimeMaterialDataType | null>(async resolve => {
      try {
        /** 查找排期数据 */
        const scheduleData = await this.seekCurTimeScheduleData();
        if (scheduleData) {
          // 是否有未下载的素材
          if (scheduleData.waitdownloadMaterialList.length) {
            if (this.#isDownMaterial) {
              fso.info("正在下载素材中");
            } else {
              this.downMaterialData({
                materialList: scheduleData.waitdownloadMaterialList,
                limit: this.#limit
              });
            }
          }

          // 已经下载完成的素材数据
          if (scheduleData.downloadedMaterialList.length) {
            // 查找当前时间需要播放的素材数据
            const curMaterialIdx = scheduleData.downloadedMaterialList.findIndex(e => {
              if (e.startTime && e.endTime) {
                const startTimeNum = timeToSecond(e.startTime);
                const endTimeNum = timeToSecond(e.endTime);
                const curTime = getFmtTime("HH:mm:ss");
                const curTimeNum = timeToSecond(curTime);

                // 当前时间大于或等于开始时间 并且 当前时间小于结束时间
                return curTimeNum >= startTimeNum && curTimeNum < endTimeNum;
              } else {
                fso.warn("此素材没有指定 startTime 和 endTime, 请检查后台排期关联素材数据", e.name);
                return false;
              }
            });

            if (curMaterialIdx !== -1) {
              const curMaterial = scheduleData.downloadedMaterialList[curMaterialIdx];
              this.log("查询到当前时间段需要播放的素材数据: ", curMaterial);

              // 根据 sort 获取下一播放的素材信息
              let nextMaterial = scheduleData.downloadedMaterialList.find(e => e.sort === curMaterial.sort + 1);

              // 是当前素材结束时间为开始时间的
              if (!nextMaterial) {
                nextMaterial = scheduleData.downloadedMaterialList.find(e => e.startTime === curMaterial.endTime);
              }

              // 下一索引的
              if (!nextMaterial) {
                nextMaterial = scheduleData.downloadedMaterialList[curMaterialIdx + 1];
              }

              resolve({
                curMaterial,
                nextMaterial,
                curSchedule: scheduleData.scheduleList
              });
            } else {
              this.log("没有查询到当前时间段需要播放的素材数据");
              const len = scheduleData.downloadedMaterialList.length;
              if (len) {
                const curMaterial = scheduleData.downloadedMaterialList[0];
                this.log("本地有下载好的素材, 播放第一个素材: ", curMaterial.name);
                resolve({
                  curMaterial: curMaterial,
                  nextMaterial: null,
                  curSchedule: scheduleData.scheduleList
                });
              } else {
                resolve(null);
              }
            }
          }
        } else {
          resolve(null);
        }
      } catch (err) {
        this.log("err: ", err);
        resolve(null);
      }
    });
  }

  /**
   * 清除本地数据库排期
   */
  public clearSchedule() {
    return new Promise<boolean>(async resolve => {
      try {
        if (scheduleSqlh) {
          this.log("清空排期数据");
          const res = await scheduleSqlh.clearTable();
          this.log("清空排期数据sql响应", res);
        } else {
          fso.error("排期数据库初始化失败");
        }
        resolve(true);
      } catch (err) {
        this.log("err: ", err);
        fso.error("清除排期发生了错误: ", err);
        resolve(false);
      }
    });
  }

  /**
   * 清除的素材数据
   * @param isDelMaterialFile 是否删除本地已经下载好的素材文件
   * @returns
   */
  public clearMateria(isDelMaterialFile: boolean) {
    return new Promise<boolean>(async resolve => {
      try {
        if (materialSqlh) {
          if (isDelMaterialFile) {
            const materialList = await materialSqlh.getList<MaterialSqliteType[]>();
            for (let i = 0; i < materialList.length; i++) {
              const material = materialList[i];
              fso.info("删除素材文件: " + material.downFileToNativePath);
              fso.rm(material.downFileToNativePath);
              fso.info("删除成功: ", material.downFileToNativePath);
            }
          }
          this.log("清空本地素材数据库数据");
          const res = await materialSqlh.clearTable();
          this.log("清空素材数据sql响应", res);
          this.playPolyfill();
        } else {
          fso.error("素材数据库初始化失败");
        }
        resolve(true);
      } catch (err) {
        this.log("err: ", err);
        fso.error("清除排期发生了错误: ", err);
        resolve(false);
      }
    });
  }

  /**
   * 清除的本地的排期和素材数据
   * @param isDelMaterialFile 是否删除本地已经下载好的素材文件
   * @returns
   */
  public clearNativeData(isDelMaterialFile: boolean) {
    return new Promise<boolean>(async resolve => {
      try {
        const clearScheduleRes = await this.clearSchedule();
        fso.info("本地排期数据清除: ", clearScheduleRes ? "成功" : "失败");

        const clearMateriaRes = await this.clearMateria(isDelMaterialFile);
        fso.info("本地素材数据清除: ", clearMateriaRes ? "成功" : "失败");
        resolve(clearScheduleRes && clearMateriaRes);
      } catch (err) {
        this.log("err: ", err);
        fso.error("清除排期发生了错误: ", err);
        resolve(false);
      }
    });
  }

  /**
   * 清除的旧版app本地的素材数据
   */
  public clearOldAppNativeData() {
    return new Promise<boolean>(async resolve => {
      try {
        fso.info("开始清理旧版app素材文件");
        const list = await this.getDownloadedMaterial(config.oldAppInfo.resourceDir);
        if (list.length === 0) {
          fso.info("没有获取到文件");
          resolve(true);
        } else {
          for (let i = 0; i < list.length; i++) {
            const item = list[i];
            fso.info("删除旧版app素材文件: ", item.path);
            await fso.rm(item.path);
          }
          this.#playOldAppResource = false;
          this.loopSeek();
          resolve(true);
        }
      } catch (err) {
        this.log("err: ", err);
        fso.error("清理旧版app素材文件发生了错误: ", err);
        resolve(false);
      }
    });
  }

  /**
   * 检查需要下载的素材数据, 比如: 素材是否已经下载, 当前时间段是否可以下载, 素材小于 10M 则直接下载等
   */
  public checkDownMateriaData(materialData: MaterialSqliteType): boolean {
    // 已经下载
    if (materialData.isDown === "2") {
      return false;
    }

    // 素材小于指定大小直接下载
    const isDirectDownload = Number(materialData.size) < config.directDownloadMaterialSize;
    if (isDirectDownload) {
      fso.info(`${materialData.name} 文件大小小于 ${config.directDownloadMaterialSize} 符合直接下载条件`);
      return isDirectDownload;
    } else {
      fso.info(`${materialData.name} 文件大小大于 ${config.directDownloadMaterialSize} 校验下载时间`);
      const ymd = dayjs().format("YYYY-MM-DD");
      const isSomeDate = dayjs().isBefore(dayjs(`${ymd} ${config.startDownMaterialDate}`));
      if (isSomeDate) {
        fso.info(`${materialData.name} 不能下载, 不符合下载条件: 指定下载时间 ${config.startDownMaterialDate}点后下载`);
      }
      return !isSomeDate;
    }
  }

  /**
   * 检查待播放的视频文件是否有问题, 因为 react-native-video 组件的 onError 事件在在使用本地 url 时
   * 文件不存在或文件格式有问题都不会触发(无法知道视频文件是否加载出错了)
   */
  public checkPlayMateriaData(materialData: MaterialSqliteType): Promise<{
    /** 素材是否完整正确 */
    flag: boolean;
    /** 错误提示 */
    msg: string;
    /**
     * 错误码
     * - 0 正常
     * - 1 文件不存在或者已经存在, 但是存在异常
     * - 2 其它错误
     */
    code: 0 | 1 | 2;
  }> {
    return new Promise(async resolve => {
      const path = materialData.downFileToNativePath;
      const fmtPath = fso.formatPath(path);
      const size = Number(materialData.size);

      this.log("检查文件路径是否存在", path);
      const isExists = await RNFS.exists(path);
      if (!isExists) {
        resolve({ flag: false, msg: `文件不存在, 请检查: ${fmtPath}`, code: 1 });
      }

      const stat = await RNFS.stat(path);
      this.log("文件详细信息: ", stat);

      if (stat.isDirectory()) {
        resolve({ flag: false, msg: `视频文件异常, 检测到是一个"目录", 请检查: ${fmtPath}`, code: 2 });
      }

      this.log("检查文件大小是否一致", `本地文件: ${stat.size}, 素材数据: ${materialData.size}`);
      if (stat.size !== size) {
        fso.warn(
          `文件异常, 本地文件大小(${stat.size}bytes)跟后台素材数据(${size}bytes)不一致, 请检查本地文件: ${fmtPath}`
        );

        // TODO 当素材有问题会一直写入日志, 导致大量的日志
        // resolve({
        //   flag: false,
        //   msg: `文件异常, 本地文件大小(${stat.size}bytes)跟后台素材数据(${size}bytes)不一致, 请检查本地文件: ${fmtPath}`,
        //   code: 1
        // });
      }

      // 扩展名检查
      // if (stat.name) {
      //   const ext = stat.name.split(".").pop();
      //   fso.info("扩展名检查", `${ext}`);
      //   if (ext !== "mp4") {
      //     resolve({ flag: false, msg: `不支持的视频文件("${ext}"), 请检查: ${fmtPath}`, code: 2 });
      //   }
      // }
      resolve({ flag: true, msg: "", code: 0 });
    });
  }

  /** 记录本地所有排期和素材数据到日志中 */
  public async recordNativeDbInfo() {
    try {
      if (scheduleSqlh) {
        const res1 = await scheduleSqlh.getList();
        fso.info("本地保存的所有排期数据: ", res1);
      } else {
        fso.warn("排期SQLite数据库初始化失败");
      }
      if (materialSqlh) {
        const res2 = await materialSqlh.getList();
        fso.info("本地保存的所有素材数据: ", res2);
      } else {
        fso.warn("素材SQLite数据库初始化失败");
      }
    } catch (err) {
      fso.error("日志数据记录失败: ", err);
    }
  }

  /** 循环查询素材数据 */
  public async loopSeek() {
    if (Date.now() - this.#lastLoopSeekTime < this.#loopSeekInterval) {
      this.log(`查找素材间隔太快, 距离上次查找不足 ${this.#loopSeekInterval / 1000}s`);
      this.delayLoopSeek(this.#loopSeekInterval);
      return;
    }
    this.#lastLoopSeekTime = Date.now();

    // 先停止之前的延时器
    this.stopSeek();

    this.isLoopSeek = true;
    try {
      // 查找排期
      const seekRes = await this.seekCurTimeMaterialData();
      if (seekRes) {
        // 排期数据存到 redux
        store.dispatch(setCurSchedule(seekRes.curSchedule));

        // TODO 播放素材的方式使用排期的数据控制

        // if (this.#isFirshLoopSeek) fso.info("========== 根据素材的时间进行播放 ==========");
        // this.playMaterialByTime(seekRes);

        if (this.#isFirshLoopSeek) fso.info("========== 根据排期数据依次播放所有已下载的素材 ==========");
        this.playMaterialByLoop(seekRes);
      } else {
        const ret = await this.isOldAppMigration();
        if (ret) {
          if (this.#isFirshLoopSeek) {
            fso.info("========== 依次播放旧版app已经下载的素材 ==========");
            this.#playOldAppResource = true;
          }
          if (!this.#playOldAppResource) {
            fso.warn(`没有查询到当前时间${dayjs().format("YYYY-MM-DD HH:mm:ss")}需要播放的排期数据`);
          }
          this.removeOldResourceOtherFile();
          this.playMaterialByFile(config.oldAppInfo.resourceDir);
        } else {
          if (this.#isFirshLoopSeek) fso.info("========== 依次播放所有已下载的素材 ==========");
          this.playMaterialByFile();
        }
      }
    } catch (err) {
      this.log("err: ", err);
    } finally {
      this.#isFirshLoopSeek = false;
    }
  }

  /** 删除旧版资源目录中不支持播放的素材 */
  public async removeOldResourceOtherFile() {
    try {
      // 删除该目录下面的所有的APK文件
      const dirs = await fso.readDir(config.oldAppInfo.resourceDir);
      for (let i = 0; i < dirs.length; i++) {
        const item = dirs[i];
        if (item.path.endsWith(".apk")) {
          fso.info("删除apk: ", item.path);
          await fso.rm(item.path);
        }

        if (item.path.endsWith(".docx")) {
          fso.info("删除docx: ", item.path);
          await fso.rm(item.path);
        }
      }
    } catch (err) {
      fso.error("发生了错误: ", err);
    }
  }

  /** 停止素材查找 */
  public stopSeek() {
    this.#timer && clearTimeout(this.#timer);
    this.isLoopSeek = false;
    // fso.info("停止定时查找本地素材数据");
  }

  /**
   * 当查询过快, 或者重复查询素材时, 使用这个查询
   * 因为有时定时器不准, 素材下载太快, 等原因都会导致查询太快, 这个方法会在指定时间后继续查询一次
   */
  public delayLoopSeek(delay: number) {
    this.log(`${delay}ms后继续查询播放的素材`);
    this.#timer && clearTimeout(this.#timer);
    // @ts-ignore
    this.#timer = setTimeout(() => {
      this.loopSeek();
    }, delay);
  }

  /** 根据时间进行播放素材 */
  public async playMaterialByTime(seekRes: SeekCurTimeMaterialDataType) {
    try {
      const { curMaterial, nextMaterial, curSchedule } = seekRes;

      if (store.getState().index.curPlayData.filePath === curMaterial.downFileToNativePath) {
        const nextMaterialInterval = 30 * 1000;
        fso.info(
          `切换的素材和当前播放的素材相同, 不切换, ${nextMaterialInterval}ms后继续查询播放的素材: `,
          curMaterial.downFileToNativePath
        );
        this.delayLoopSeek(nextMaterialInterval);
      } else {
        fso.info("检验素材数据完整性: ", curMaterial.downFileToNativePath);
        const res = await this.checkPlayMateriaData(curMaterial);
        if (res.flag) {
          const playData = this.buildPlayDataByMaterial(curMaterial);
          store.dispatch(setCurPlayData(playData));
          store.dispatch(setNextMaterial(nextMaterial));
          fso.info("当前播放的素材为: ", playData.rawData?.name);

          // 如果当前播放的素材为PDF, 则不用计算下一播放的素材时间, 因为 pdfOnLoadComplete 里面有计算时间
          if (playData.type === '2') return;

          if (nextMaterial) {
            // 当前时间总毫秒数
            const curTimeNum = timeToSecond(getFmtTime("HH:mm:ss"));

            // 下一个素材开始时间总毫秒数
            const nextStartTime = timeToSecond(getFmtTime(nextMaterial.startTime));

            // 当前时间 减去 下一个素材开始时间 得到 多少毫秒后播放下一个素材视频
            let nextMaterialInterval = (nextStartTime - curTimeNum) * 1000;

            // 素材时间异常, 即下一个播放的素材时间是早于当前播放素材的时长
            if (nextMaterialInterval <= 0) {
              // 下一个播放的时长为当前素材的时长
              nextMaterialInterval = timeToSecond(curMaterial.duration) * 1000;
              fso.info("下一个播放素材的时间异常: ", nextMaterial);
              fso.info("取素材的时长作为间隔: ", nextMaterial.duration);
            }

            // 查找素材播放需要加多5秒钟, 因为定时器可能不是很准
            nextMaterialInterval += this.#deviationInterval;

            fso.info(`${nextMaterialInterval}ms后更换为下一素材: `, nextMaterial);
            this.#nextPlayFilePath = nextMaterial.downFileToNativePath;

            // 清空之前的延时器
            this.#timer && clearTimeout(this.#timer);

            // 延迟到指定的时间继续查找素材播放
            // @ts-ignore
            this.#timer = setTimeout(() => {
              this.loopSeek();
            }, nextMaterialInterval);
          } else {
            fso.info(`当前素材没有下一需要播放的素材, 一直播放该素材`);
          }
        } else {
          fso.error("素材检验不通过, 错误原因: ", res.msg);
          this.playPolyfill();

          // TODO 通知后台

          if (res.code === 1) {
            fso.info(`删除该素材并重新下载: `, curMaterial, curMaterial.downFileToNativePath);
            fso.rm(curMaterial.downFileToNativePath);
            this.downMaterialData({
              materialList: [curMaterial],
              limit: this.#limit
            });
          }
        }
      }
    } catch (err) {
      fso.error("根据时间进行素材播放失败: ", err);
    }
  }

  /**
   * 循环播放所有已下载的素材
   * @param seekRes 查找的素材
   * @param nextPlayId 是否指定要播放的素材id(这个id是本地sqlite的id)
   */
  public async playMaterialByLoop(seekRes: SeekCurTimeMaterialDataType, nextPlayId?: string) {
    try {
      const allMaterial = seekRes.curSchedule.flatMap(e => e.materialList);

      if (allMaterial.length === 0) {
        fso.info(`排期下面没有需要播放的素材: `, seekRes.curSchedule.map(e => e.name).join(", "));
        this.playPolyfill();
        return;
      }

      const curPlayData = store.getState().index.curPlayData;
      const sourceId = curPlayData.rawData?.sourceId;

      const isNextPlayId = typeof nextPlayId !== "undefined";
      if (sourceId || isNextPlayId) {
        const idx = allMaterial.findIndex(e => e.sourceId === sourceId);
        let nextIdx = 0;
        if (sourceId) {
          if (idx !== -1) {
            nextIdx = idx + 1 >= allMaterial.length ? 0 : idx + 1;
          }
        }

        // 有指定素材则使用指定的素材
        if (isNextPlayId) {
          this.#lastLoopSeekTime = 0;
          nextIdx = allMaterial.findIndex(e => e.sourceId === nextPlayId);
        }

        if (nextIdx < 0) nextIdx = 0;
        this.log("播放的素材索引: ", nextIdx);

        const nextMaterial = allMaterial[nextIdx];

        this.log("校验下一素材数据完整性: ", nextMaterial);

        const res = await this.checkPlayMateriaData(nextMaterial);
        // TODO 如果有一个素材下载完成的, 但是下一播放的素材未下载完成, 则这里的判断会播放垫片, 正常来说是要一直播放第一个下载好的素材
        if (res.flag) {
          store.dispatch(setNextMaterial(nextMaterial));
          this.#nextPlayFilePath = nextMaterial.downFileToNativePath;

          // 图片播放固定时间, 视频则播放完全
          let nextMaterialInterval = this.loopPlayInterval;

          if (curPlayData.rawData) {
            nextMaterialInterval = timeToSecond(curPlayData.rawData.duration) * 1000;
          } else {
            fso.warn("没有获取到当前播放素材的数据: ", curPlayData.rawData);
          }

          /**
           * 有指定素材则是当前正在播放错误或者垫片
           * PDF则在 pdfOnLoadComplete 方法里面自动延迟了时长
           * 所以这两种情况都不需要延迟直接播放下一素材
           */
          nextMaterialInterval = isNextPlayId || curPlayData.type === "2" ? 0 : nextMaterialInterval;
          // nextMaterialInterval = isNextPlayId ? 0 : nextMaterialInterval;
          this.log(`${nextMaterialInterval}ms后更换为下一素材`, nextMaterial.name);

          // 清空之前的延时器
          this.#timer && clearTimeout(this.#timer);

          // 延迟到指定的时间继续查找素材播放
          // @ts-ignore
          this.#timer = setTimeout(() => {
            const playData = this.buildPlayDataByMaterial(nextMaterial);
            store.dispatch(setCurPlayData(playData));
            fso.info(
              `播放素材, 类型: ${playData.type}, 时长: ${playData.rawData?.duration}, 路径: ${playData.filePath}`
            );

            // PDF这里不用轮播, 因为pdfOnLoadComplete 里面会有轮播的逻辑
            if (playData.type !== "2") {
              this.loopSeek();
            }
          }, nextMaterialInterval);
        } else {
          fso.error("下一个播放的素材检验不通过, 错误原因: ", res.msg);
          if (res.code === 1) {
            fso.info(`删除该素材并重新下载: `, nextMaterial.name, nextMaterial.downFileToNativePath);
            fso.rm(nextMaterial.downFileToNativePath);
            this.downMaterialData({
              materialList: [nextMaterial],
              limit: this.#limit
            });
          }
        }
      } else {
        fso.info("没有获取到当前播放素材的 id: ", curPlayData.type);
        if (["ERROR", "POLYFILL"].includes(curPlayData.type)) {
          fso.info(`当前正在播放垫片或者错误: ${curPlayData.type}, 播放第一个素材`, allMaterial[0].name);
          this.playMaterialByLoop(seekRes, allMaterial[0].sourceId);
        } else {
          fso.error("没有获取到当前播放素材的 id: ", curPlayData.rawData);
          this.playPolyfill();
        }
      }
    } catch (err) {
      fso.error("循环播放所有已下载的素材素材: ", err);
    }
  }

  /**
   * 根据文件循环播放素材
   */
  public async playMaterialByFile(targetDir?: string) {
    try {
      const downloadedMaterial = await this.getDownloadedMaterial(targetDir);
      if (downloadedMaterial.length === 0) {
        fso.warn("本地没有已经下载的素材: ", targetDir);
        this.playPolyfill();
      } else {
        let nextPlayData: CurPlayDataType;
        let nextPlayFilePath: string;
        const curPlayData = store.getState().index.curPlayData;
        if (["ERROR", "POLYFILL"].includes(curPlayData.type)) {
          fso.info(
            `读取到本地存在以下的素材${downloadedMaterial.length}个: `,
            downloadedMaterial.map(e => e.path)
          );
          const curPlayFilePath = downloadedMaterial[0].path;
          fso.info(`当前正在播放垫片或者错误: ${curPlayData.type}, 播放第一个素材`, curPlayFilePath);
          nextPlayFilePath = downloadedMaterial[1] ? downloadedMaterial[1].path : curPlayFilePath;
          if (!downloadedMaterial[1]) {
            this.log("没有下一播放的素材");
          }
          nextPlayData = await this.buildPlayDataByFilePath(curPlayFilePath);
        } else {
          const idx = downloadedMaterial.findIndex(e => e.path === curPlayData.filePath);
          const nextIdx = idx + 1 >= downloadedMaterial.length ? 0 : idx + 1;
          nextPlayFilePath = downloadedMaterial[nextIdx].path;
          nextPlayData = await this.buildPlayDataByFilePath(nextPlayFilePath);
        }

        if (!nextPlayFilePath) {
          fso.error("文件地址解析错误, 播放第一个素材");
          nextPlayData = await this.buildPlayDataByFilePath(downloadedMaterial[0].path);
        }

        this.#nextPlayFilePath = nextPlayFilePath;

        // 图片, 垫片和错误直接轮播
        // if (["1", "2", "ERROR", "POLYFILL"].includes(nextPlayData.type)) {
        if (["1", "ERROR", "POLYFILL"].includes(nextPlayData.type)) {
          if (nextPlayData.filePath !== curPlayData.filePath) {
            fso.info(`播放 ${nextPlayData.filePath}, 时长: ${this.loopPlayInterval}ms`);
            store.dispatch(setCurPlayData(nextPlayData));
          }

          // 清空之前的延时器
          this.#timer && clearTimeout(this.#timer);

          // 延迟到指定的时间继续查找素材播放
          // @ts-ignore
          this.#timer = setTimeout(() => {
            this.loopSeek();
          }, this.loopPlayInterval);

          // PDF和视频需要对应组件触发 onLoad 拿到需要的数据后计算时间轮播
        } else if (["2", "4"].includes(nextPlayData.type)) {
          if (nextPlayData.filePath !== curPlayData.filePath) {
            fso.info(`播放 ${nextPlayFilePath}`);
            store.dispatch(setCurPlayData(nextPlayData));
          }
        } else {
          fso.error("暂不支持的播放类型: ", nextPlayData);
        }
      }
    } catch (err) {
      console.log("根据文件循环播放素材失败: ", err);
      fso.error("根据文件循环播放素材失败: ", err);
      this.playPolyfill();
    }
  }

  /** 播放器 onLoad 事件, 这里是为了取到视频的时长 */
  public videoOnload(e: OnLoadData) {
    // 当使用 playMaterialByFile 方法轮播时, 是取不到视频的时长的, 这里使用播放器组件解析的时长
    if (this.#playOldAppResource) {
      if (!this.#nextPlayFilePath) {
        fso.error("没有获取到下一需要播放的文件地址");
      }

      // 视频的时长
      const duration = Math.floor(e.duration);
      const nextMaterialInterval = duration * 1000;

      fso.info(`取视频时长作为间隔 ${nextMaterialInterval}ms, 后查找下一素材`);

      // 清空之前的延时器
      this.#timer && clearTimeout(this.#timer);

      // 延迟到指定的时间继续查找素材播放
      // @ts-ignore
      this.#timer = setTimeout(() => {
        this.loopSeek();
      }, nextMaterialInterval);
    }
  }

  /** PDF组件 onLoadComplete 事件, 这里是为了取到PDF的总页数 */
  public pdfOnLoadComplete(numberOfPages: number, filePath: string, setPdfPagecount: (count: number) => void) {
    if (!this.#nextPlayFilePath) {
      fso.error("没有获取到下一需要播放的文件地址");
    }

    const defaultDuration = "00:00:20";
    const { curPlayData } = store.getState().index;
    const rawData = curPlayData?.rawData;
    if (!rawData?.duration) {
      fso.error("没有获取到当前播放的素材的持续时间, 使用默认播放时长: ", defaultDuration);
    }

    const isSwitch = this.#pdfCurrentInfo.filePath !== filePath;
    if (isSwitch) {
      this.#pdfCurrentInfo.filePath = filePath;
      this.#pdfCurrentInfo.page = 1;
    }

    const duration = timeToSecond(rawData?.duration || defaultDuration);
    const nextPageInterval = duration * 1000;
    fso.info(`PDF当前播放页【${this.#pdfCurrentInfo.page}】, 切换到下一页的间隔 ${nextPageInterval}ms`);

    this.#pdfSetTimeoutTimer && clearTimeout(this.#pdfSetTimeoutTimer);
    this.#pdfSetTimeoutTimer = setTimeout(() => {
      const nextPage = this.#pdfCurrentInfo.page + 1;
      if (nextPage > numberOfPages) {
        fso.info(`PDF已轮播完成播放下一素材`);
        this.#pdfCurrentInfo.page = 1;
        this.#pdfCurrentInfo.filePath = "";
        this.loopSeek();
      } else {
        this.#pdfCurrentInfo.page = nextPage;
        setPdfPagecount(nextPage);
      }
    }, nextPageInterval);
  }

  /** 解析文件返回指定的素材 type */
  public parseFileToType(filePath: string) {
    return new Promise<CurPlayDataType["type"]>(async resolve => {
      try {
        const exists = await RNFS.exists(filePath);
        if (exists) {
          const stat = await RNFS.stat(filePath);
          const fileName = stat.path.split("/").pop()?.toLowerCase();
          if (fileName) {
            if (["jpg", "jpeg", "png"].some(e => fileName.endsWith(e))) {
              resolve("1");
            } else if (["pdf"].some(e => fileName.endsWith(e))) {
              resolve("2");
            } else if (["mp4", "m4v"].some(e => fileName.endsWith(e))) {
              resolve("4");
            } else {
              fso.error("不支持的文件后缀名, 请检查: ", filePath);
              resolve("POLYFILL");
            }
          } else {
            fso.error("文件后缀名解析失败, 请检查: ", filePath);
            resolve("POLYFILL");
          }
        } else {
          fso.error("解析的文件不存在, 请检查: ", filePath);
          resolve("POLYFILL");
        }
      } catch (err) {
        fso.error("解析文件出错: ", err);
        resolve("POLYFILL");
      }
    });
  }

  /** 根据素材数据构建播放器需要的数据 */
  public buildPlayDataByMaterial(material: MaterialSqliteType): CurPlayDataType {
    // 截取分辨率
    let [width, height] = material.resolution.split("*").map(Number);
    const window = Dimensions.get("window");

    if (width > window.width) {
      this.log(`素材数据的width分辨率太大了【${width}】, 设备最大分辨率为【${window.width}】, 素材的分辨率自适应`);
      width = 0;
    }

    if (height > window.height) {
      this.log(`素材数据的height分辨率太大了【${height}】, 设备最大分辨率为${window.height}】, 素材的分辨率自适应`);
      height = 0;
    }

    if (width === 0 || height === 0) {
      width = 0;
      height = 0;
    }

    return {
      type: material.type,
      filePath: material.downFileToNativePath,
      content: "",
      errorText: "",
      rawData: material,
      width,
      height
    };
  }

  /** 根据文件数据构建播放器需要的数据 */
  public buildPlayDataByFilePath(filePath: string) {
    return new Promise<CurPlayDataType>(async resolve => {
      try {
        // 现在本地根据下载路径查看有没有对应的素材数据
        const res = await materialSqlh?.getByCustom<MaterialSqliteType>({
          downFileToNativePath: filePath
        });
        const tagetMaterial = res && res.length ? res[0] : null;

        this.log("本地找到对应的素材数据: ", tagetMaterial);

        const type = await this.parseFileToType(filePath);
        resolve({
          type,
          filePath,
          content: "",
          errorText: "",
          rawData: tagetMaterial,
          width: 0,
          height: 0
        });
      } catch (err) {
        fso.error("err", err);
        resolve({
          type: "POLYFILL",
          filePath,
          content: "",
          errorText: "",
          rawData: null,
          width: 0,
          height: 0
        });
      }
    });
  }

  /** 播放垫片 */
  public playPolyfill() {
    fso.info("播放垫片");

    store.dispatch(
      setCurPlayData({
        type: "POLYFILL",
        filePath: "",
        rawData: null,
        content: "",
        errorText: "",
        width: 0,
        height: 0
      })
    );

    // store.dispatch(setNextMaterial(null));

    // 先停止之前的延时器
    this.stopSeek();
  }

  /** 获取本地已下载的素材信息 */
  getDownloadedMaterial(targetDir = fso.resourceDir) {
    return new Promise<RNFS.ReadDirItem[]>(async resolve => {
      const reslut: RNFS.ReadDirItem[] = [];
      try {
        const exists = await fso.exists(targetDir);
        if (!exists) {
          this.log("该目录不存在: ", targetDir);
          resolve([]);
          return;
        }
        const dirs = await fso.readDir(targetDir);
        for (let i = 0; i < dirs.length; i++) {
          const dir = dirs[i];
          if (dir.isDirectory()) {
            await RNFS.scanFile(dir.path);
            // TODO 无法识别出PDF
            const materialList = await fso.readDir(dir.path);
            for (let j = 0; j < materialList.length; j++) {
              const material = materialList[j];
              if (material.isFile()) {
                reslut.push(material);
              }
            }
          } else if (dir.isFile()) {
            reslut.push(dir);
          }
        }
        resolve(reslut);
      } catch (err) {
        fso.error("获取本地素材文件信息出错: ", targetDir, err);
        resolve([]);
      }
    });
  }

  /** 是否是旧版多媒体迁移过来的 */
  isOldAppMigration() {
    return new Promise<boolean>(async resolve => {
      try {
        const list = await this.getDownloadedMaterial(config.oldAppInfo.resourceDir);
        if (list.length) {
          const done = () => resolve(list.length === 0 ? false : true);

          if (scheduleSqlh) {
            const res = await scheduleSqlh.getList().catch(err => {
              fso.error("发生了错误: ", err);
              done();
            });

            if (res && res.length) {
              resolve(false);
            } else {
              done();
            }
          } else {
            done();
          }
        } else {
          resolve(false);
        }
      } catch (err) {
        fso.error("发生了错误: ", err);
        resolve(false);
      }
    });
  }

  /** 根据本地已经下载的素材文件更新素材数据库中的下载状态字段 */
  updateMaterialIsDown() {
    return new Promise<boolean>(async resolve => {
      try {
        const items = await this.getDownloadedMaterial();
        this.log(
          "本地已经下载的素材数据: ",
          items.map(e => e.path)
        );
        if (materialSqlh) {
          const res = await materialSqlh.updataByWhen({
            datas: items,
            onceMaxUpdateDataLength: 900,
            fieldOpts: [
              {
                setField: "isDown",
                getWhenField() {
                  return "downFileToNativePath";
                },
                getWhenValue(row) {
                  return row.path;
                },
                getThenValue(row) {
                  return "2";
                }
              }
            ],
            getExtraWhere(updatas) {
              return updatas.map(e => `downFileToNativePath="${e.path}"`).join(" OR ");
            }
          });
          fso.info("本地素材下载状态更新成功: ", res);
          resolve(true);
        } else {
          fso.error("素材SQLite数据库初始化失败");
          resolve(false);
        }
      } catch (err) {
        this.log("err: ", err);
        fso.error("更新本地素材下载状态更新失败: ", err);
        resolve(false);
      }
    });
  }
}
